package com.itesm.distribuidos.mexicantrain;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Collections;
import java.util.Stack;


/*     SET     TILES
 * ---------------------
 * 	Double-6 	28
 *	Double-9 	55
 *	Double-12 	91
 *	Double-15 	136
 *	Double-18 	190
 */

public class MexicanTrainServer extends Thread {
	private Socket[] clients;
	private Socket anyRequest, updater;
	private ServerSocket socket, threadSocket;
	private Stack<Domino> mainSuit, boneyard;
	private Stack<Integer> initialTurns;
	private ObjectInputStream ois;
	private GameBoard mainBoard;
	private ServerGUI serverGUI;
	private Object request;
	private int setType = 12;
	private int numPasses = 0;
	private int rounds = 12;
	private int numPlayers, turnNumber;
	private boolean gameStarted, gameStalled, nextRound;
	private int maxNumPlayers;
	private boolean running;

	public MexicanTrainServer(int maxPlayers, ServerGUI gui) {
		serverGUI = gui;
		startRound(rounds);
		maxNumPlayers = maxPlayers;
		running = true;
		try {
			socket = new ServerSocket(60000);
			threadSocket = new ServerSocket(61000);
		}
		catch (IOException ioe) { serverGUI.updateConsole("Server - IO Exception creating the socket"); }
	}

	public void startRound(int round) {
		serverGUI.updateConsole("\nNew Round Started\n");
		createSuit(round);
		shuffleSuit();
		printSuit();
		numPlayers = 0;
		turnNumber = 1;
		gameStarted = false;
		gameStalled = false;
		nextRound = false;
		mainBoard = new GameBoard(round);
		clients = new Socket[8];
		initialTurns = new Stack<Integer>();
	}

	public void run() {
		while(running) {
			try {
				if (!gameStarted) {
					socket.setSoTimeout(500);
					clients[numPlayers] = socket.accept();
					ois = new ObjectInputStream(clients[numPlayers].getInputStream());
				}
				else {
					anyRequest = socket.accept();
					ois = new ObjectInputStream(anyRequest.getInputStream());
				}
				request = ois.readObject();
				if (request instanceof Integer) { doOperation((Integer) request); }
				if (request instanceof Domino) {
					Domino playedTile = (Domino) request;
					numPasses = 0;
					mainBoard.setTile(playedTile);
					if (playedTile.isDouble()) { turnNumber--; }
					if (playedTile.isFinal()) { clientUpdate(true,false,true); }
					else { clientUpdate(false,false,false); }
				}
			}
			catch (SocketException se) { serverGUI.updateConsole("[Timeout] Playing...."); }
			catch (IOException ioe) { }
			catch (ClassNotFoundException cnfe) { serverGUI.updateConsole("Server - Class Not Found Error @run"); }
		}
	}

	public GameBoard getGameBoard() {
		return mainBoard.getBoard();
	}

	public void clientUpdate(boolean clientDrew, boolean clientPass, boolean endGame) {
		if (clientPass) {
			mainBoard.changePlayerAvailability(turnNumber);
			numPasses++;
			if (numPasses == numPlayers) { gameStalled = true; }
		}
		if (!clientDrew) {
			if (turnNumber == numPlayers) { turnNumber = 0; }
			turnNumber++;
		}
		for (int i=0; i<numPlayers; i++) {
			try {
				updater = threadSocket.accept();
				ObjectOutputStream oos = new ObjectOutputStream(updater.getOutputStream());
				oos.flush();
				if (!gameStalled) {
					if (!clientDrew) { oos.writeObject(turnNumber); }
					else if (endGame) {
						oos.writeObject(100);
						nextRound = true;
					}
	                else { oos.writeObject(Integer.toString(boneyard.size())); }
				}
				else {
					oos.writeObject(200);
					nextRound = true;
				}
				oos.close();
				updater.close();
			}
			catch (IOException ioe) { serverGUI.updateConsole("Server - IO Error @clientUpdate"); }
		}
		if (nextRound) {
			rounds--;
			startRound(rounds);
		}
	}

	public void startGame() {
		if (gameStarted)
			return;
		gameStarted = true;
		try { Thread.sleep(3000); }
		catch (InterruptedException ie) { }
		for (int i=0; i<numPlayers; i++) {
			try {
				ObjectOutputStream oos = new ObjectOutputStream(clients[i].getOutputStream());
				oos.flush();
				oos.writeObject(getHand());
				oos.close();
				clients[i].close();
			}
			catch (IOException ioe) { serverGUI.updateConsole("Server - IO Exception @startGame"); }
		}
		fillBoneyard();
		clientUpdate(true,false,false);
	}

	public void doOperation(int operation) {
		if (operation == 0) {
			numPlayers++;
			initialTurns.push(numPlayers);
			serverGUI.updateConsole("Current Players: " + numPlayers);
			if (numPlayers == maxNumPlayers) { startGame(); }
		}
		else {
			try {
				ObjectOutputStream oos = new ObjectOutputStream(anyRequest.getOutputStream());
				oos.flush();
				if (operation == 1) { oos.writeObject(pickFromBoneyard());	}
				if (operation == 2) { oos.writeObject(getGameBoard());		}
				if (operation == 3) { oos.writeObject(assignTurn());		}
				if (operation == 4) { clientUpdate(false,true,false);		}
				if (operation == 5) { mainBoard.closeLine(turnNumber);      }
				if (operation == 6) { mainBoard.lockLine();					}
				oos.close();
				anyRequest.close();
			}
			catch (IOException ioe) { serverGUI.updateConsole("Server - IO Exception @doOperation: " + operation); }
		}
	}

	public void createSuit(int round) {
		mainSuit = new Stack<Domino>();
		for (int i=0; i<setType+1; i++) {
			for (int j=i; j<setType+1; j++) {
				if (i == j && i != round) { mainSuit.push(new Domino(true,i,j)); }
				else if (i != round || j != round) { mainSuit.push(new Domino(false,i,j)); }
			}
		}
	}

	public Stack<Domino> getHand() {
		Stack<Domino> aHand = new Stack<Domino>();
		for (int i=0; i<maxTiles(); i++) { aHand.push(mainSuit.pop()); }
		return aHand;
	}

	public int maxTiles() {
		if (numPlayers == 2 || numPlayers == 3) { return 16; }
		else if (numPlayers == 4) { return 15; }
		else if (numPlayers == 5) { return 14; }
		else if (numPlayers == 6) { return 12; }
		else if (numPlayers == 7) { return 10; }
		else if (numPlayers == 8) { return 9;  }
		else { return (Integer) null; }
	}

	public void fillBoneyard() {
		boneyard = new Stack<Domino>();
		while (!mainSuit.isEmpty()) { boneyard.push(mainSuit.pop()); }
	}

	public Domino pickFromBoneyard() {
		if (!boneyard.isEmpty()) {
			numPasses = 0;
			Domino next = boneyard.pop();
			clientUpdate(true,false,false);
			return next;
		}
		else { return null; }
	}

	public void shuffleSuit() {
		Collections.shuffle(mainSuit);
		Collections.shuffle(mainSuit);
		Collections.shuffle(mainSuit);
	}

	public void printSuit() {
		for (int i=0; i<mainSuit.size(); i++) {
			serverGUI.updateConsole("Server Tile #" + i + " : " + mainSuit.elementAt(i).getSideA() + " | " + mainSuit.elementAt(i).getSideB());
		}
		serverGUI.updateConsole(" ");
	}

	public int assignTurn() {
		int turn = initialTurns.lastElement();
		initialTurns.remove(initialTurns.lastElement());
		return turn;
	}

	public int getNumPlayers(){
		return numPlayers;
	}

	public void stopServer() {
		interrupt();
		running = false;
		closeSockets();
	}

	private void closeSockets() {
		try {
			socket.close();
			threadSocket.close();
			for(int i = 0; i < numPlayers; i++) { clients[i].close(); }
		}
		catch (IOException ioe) { serverGUI.updateConsole("Error closing sockets!"); }
	}
}
